/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.mail;

import java.lang.reflect.*;
import java.io.*;
import java.net.*;
import java.security.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.activation.*;

import com.sun.mail.util.LineInputStream;

/**
 * The Session class represents a mail session and is not subclassed.
 * It collects together properties and defaults used by the mail API's.
 * A single default session can be shared by multiple applications on the
 * desktop. Unshared sessions can also be created.
 * <p>
 * 
 * The Session class provides access to the protocol providers that implement
 * the <code>Store</code>, <code>Transport</code>, and related classes. The
 * protocol providers are configured using the following files:
 * <ul>
 * <li> <code>javamail.providers</code> and
 * <code>javamail.default.providers</code></li>
 * <li> <code>javamail.address.map</code> and
 * <code>javamail.default.address.map</code></li>
 * </ul>
 * <p>
 * Each <code>javamail.</code><i>X</i> resource file is searched for using three
 * methods in the following order:
 * <ol>
 * <li> <code>java.home/lib/javamail.</code><i>X</i></li>
 * <li> <code>META-INF/javamail.</code><i>X</i></li>
 * <li> <code>META-INF/javamail.default.</code><i>X</i></li>
 * </ol>
 * <p>
 * The first method allows the user to include their own version of the resource
 * file by placing it in the <code>lib</code> directory where the
 * <code>java.home</code> property points. The second method allows an
 * application that uses the JavaMail APIs to include their own resource files
 * in their application's or jar file's <code>META-INF</code> directory. The
 * <code>javamail.default.</code><i>X</i> default files are part of the JavaMail
 * <code>mail.jar</code> file.
 * <p>
 * 
 * File location depends upon how the <code>ClassLoader</code> method
 * <code>getResource</code> is implemented. Usually, the
 * <code>getResource</code> method searches through CLASSPATH until it finds the
 * requested file and then stops. JDK 1.1 has a limitation that the number of
 * files of each name that will be found in the CLASSPATH is limited to one.
 * However, this only affects method two, above; method one is loaded from a
 * specific location (if allowed by the SecurityManager) and method three uses a
 * different name to ensure that the default resource file is always loaded
 * successfully. J2SE 1.2 and later are not limited to one file of a given name.
 * <p>
 * 
 * The ordering of entries in the resource files matters. If multiple entries
 * exist, the first entries take precedence over the later entries. For example,
 * the first IMAP provider found will be set as the default IMAP implementation
 * until explicitly changed by the application. The user- or system-supplied
 * resource files augment, they do not override, the default files included with
 * the JavaMail APIs. This means that all entries in all files loaded will be
 * available.
 * <p>
 * 
 * <b><code>javamail.providers</code></b> and <b>
 * <code>javamail.default.providers</code></b>
 * <p>
 * 
 * These resource files specify the stores and transports that are available on
 * the system, allowing an application to "discover" what store and transport
 * implementations are available. The protocol implementations are listed one
 * per line. The file format defines four attributes that describe a protocol
 * implementation. Each attribute is an "="-separated name-value pair with the
 * name in lowercase. Each name-value pair is semi-colon (";") separated. The
 * following names are defined.
 * <p>
 * 
 * <table border=1>
 * <caption> Attribute Names in Providers Files </caption>
 * <tr>
 * <th>Name</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>protocol</td>
 * <td>Name assigned to protocol. For example, <code>smtp</code> for Transport.</td>
 * </tr>
 * <tr>
 * <td>type</td>
 * <td>Valid entries are <code>store</code> and <code>transport</code>.</td>
 * </tr>
 * <tr>
 * <td>class</td>
 * <td>Class name that implements this protocol.</td>
 * </tr>
 * <tr>
 * <td>vendor</td>
 * <td>Optional string identifying the vendor.</td>
 * </tr>
 * <tr>
 * <td>version</td>
 * <td>Optional string identifying the version.</td>
 * </tr>
 * </table>
 * <p>
 * 
 * Here's an example of <code>META-INF/javamail.default.providers</code> file
 * contents:
 * 
 * <pre>
 * protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Sun Microsystems, Inc.;
 * protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Sun Microsystems, Inc.;
 * </pre>
 * <p>
 * 
 * <b><code>javamail.address.map</code></b> and <b>
 * <code>javamail.default.address.map</code></b>
 * <p>
 * 
 * These resource files map transport address types to the transport protocol.
 * The <code>getType</code> method of </code>javax.mail.Address</code> returns
 * the address type. The <code>javamail.address.map</code> file maps the
 * transport type to the protocol. The file format is a series of name-value
 * pairs. Each key name should correspond to an address type that is currently
 * installed on the system; there should also be an entry for each
 * <code>javax.mail.Address</code> implementation that is present if it is to be
 * used. For example, the <code>javax.mail.internet.InternetAddress</code>
 * method <code>getType</code> returns "rfc822". Each referenced protocol should
 * be installed on the system. For the case of <code>news</code>, below, the
 * client should install a Transport provider supporting the nntp protocol.
 * <p>
 * 
 * Here are the typical contents of a <code>javamail.address.map</code> file:
 * 
 * <pre>
 * rfc822=smtp
 * news=nntp
 * </pre>
 * 
 * @author John Mani
 * @author Bill Shannon
 * @author Max Spivak
 */

public final class Session {

	private final Properties props;
	private final Authenticator authenticator;
	private final Hashtable authTable = new Hashtable();
	private boolean debug = false;
	private PrintStream out; // debug output stream
	private final Vector providers = new Vector();
	private final Hashtable providersByProtocol = new Hashtable();
	private final Hashtable providersByClassName = new Hashtable();
	private final Properties addressMap = new Properties();
	// maps type to protocol
	// The default session.
	private static Session defaultSession = null;

	// Constructor is not public
	private Session(Properties props, Authenticator authenticator) {
		this.props = props;
		this.authenticator = authenticator;

		if (Boolean.valueOf(props.getProperty("mail.debug")).booleanValue())
			debug = true;

		if (debug)
			pr("DEBUG: JavaMail version " + Version.version);

		// get the Class associated with the Authenticator
		Class cl;
		if (authenticator != null)
			cl = authenticator.getClass();
		else
			cl = this.getClass();
		// load the resources
		loadProviders(cl);
		loadAddressMap(cl);
	}

	/**
	 * Get a new Session object.
	 * 
	 * @param props
	 *            Properties object that hold relevant properties.<br>
	 *            It is expected that the client supplies values
	 *            for the properties listed in Appendix A of the
	 *            JavaMail spec (particularly mail.store.protocol,
	 *            mail.transport.protocol, mail.host, mail.user,
	 *            and mail.from) as the defaults are unlikely to
	 *            work in all cases.
	 * @param authenticator
	 *            Authenticator object used to call back to
	 *            the application when a user name and password is
	 *            needed.
	 * @return a new Session object
	 * @see javax.mail.Authenticator
	 */
	public static Session getInstance(Properties props, Authenticator authenticator) {
		return new Session(props, authenticator);
	}

	/**
	 * Get a new Session object.
	 * 
	 * @param props
	 *            Properties object that hold relevant properties.<br>
	 *            It is expected that the client supplies values
	 *            for the properties listed in Appendix A of the
	 *            JavaMail spec (particularly mail.store.protocol,
	 *            mail.transport.protocol, mail.host, mail.user,
	 *            and mail.from) as the defaults are unlikely to
	 *            work in all cases.
	 * @return a new Session object
	 * @since JavaMail 1.2
	 */
	public static Session getInstance(Properties props) {
		return new Session(props, null);
	}

	/**
	 * Get the default Session object. If a default has not yet been
	 * setup, a new Session object is created and installed as the
	 * default.
	 * <p>
	 * 
	 * Since the default session is potentially available to all code executing
	 * in the same Java virtual machine, and the session can contain security
	 * sensitive information such as user names and passwords, access to the
	 * default session is restricted. The Authenticator object, which must be
	 * created by the caller, is used indirectly to check access permission. The
	 * Authenticator object passed in when the session is created is compared
	 * with the Authenticator object passed in to subsequent requests to get the
	 * default session. If both objects are the same, or are from the same
	 * ClassLoader, the request is allowed. Otherwise, it is denied.
	 * <p>
	 * 
	 * Note that if the Authenticator object used to create the session is null,
	 * anyone can get the default session by passing in null.
	 * <p>
	 * 
	 * Note also that the Properties object is used only the first time this
	 * method is called, when a new Session object is created. Subsequent calls
	 * return the Session object that was created by the first call, and ignore
	 * the passed Properties object. Use the <code>getInstance</code> method to
	 * get a new Session object every time the method is called.
	 * <p>
	 * 
	 * In JDK 1.2, additional security Permission objects may be used to control
	 * access to the default session.
	 * 
	 * @param props
	 *            Properties object. Used only if a new Session
	 *            object is created.<br>
	 *            It is expected that the client supplies values
	 *            for the properties listed in Appendix A of the
	 *            JavaMail spec (particularly mail.store.protocol,
	 *            mail.transport.protocol, mail.host, mail.user,
	 *            and mail.from) as the defaults are unlikely to
	 *            work in all cases.
	 * @param authenticator
	 *            Authenticator object. Used only if a
	 *            new Session object is created. Otherwise,
	 *            it must match the Authenticator used to create
	 *            the Session.
	 * @return the default Session object
	 */
	public static synchronized Session getDefaultInstance(Properties props, Authenticator authenticator) {
		if (defaultSession == null)
			defaultSession = new Session(props, authenticator);
		else {
			// have to check whether caller is allowed to see default session
			if (defaultSession.authenticator == authenticator)
				; // either same object or both null, either way OK
			else if (defaultSession.authenticator != null && authenticator != null && defaultSession.authenticator.getClass().getClassLoader() == authenticator.getClass().getClassLoader())
				; // both objects came from the same class loader, OK
			else
				// anything else is not allowed
				throw new SecurityException("Access to default session denied");
		}

		return defaultSession;
	}

	/**
	 * Get the default Session object. If a default has not yet been
	 * setup, a new Session object is created and installed as the
	 * default.
	 * <p>
	 * 
	 * Note that a default session created with no Authenticator is available to
	 * all code executing in the same Java virtual machine, and the session can
	 * contain security sensitive information such as user names and passwords.
	 * 
	 * @param props
	 *            Properties object. Used only if a new Session
	 *            object is created.<br>
	 *            It is expected that the client supplies values
	 *            for the properties listed in Appendix A of the
	 *            JavaMail spec (particularly mail.store.protocol,
	 *            mail.transport.protocol, mail.host, mail.user,
	 *            and mail.from) as the defaults are unlikely to
	 *            work in all cases.
	 * @return the default Session object
	 * @since JavaMail 1.2
	 */
	public static Session getDefaultInstance(Properties props) {
		return getDefaultInstance(props, null);
	}

	/**
	 * Set the debug setting for this Session.
	 * <p>
	 * Since the debug setting can be turned on only after the Session has been
	 * created, to turn on debugging in the Session constructor, set the
	 * property <code>mail.debug</code> in the Properties object passed in to
	 * the constructor to true. The value of the <code>mail.debug</code>
	 * property is used to initialize the per-Session debugging flag. Subsequent
	 * calls to the <code>setDebug</code> method manipulate the per-Session
	 * debugging flag and have no affect on the <code>mail.debug</code>
	 * property.
	 * 
	 * @param debug
	 *            Debug setting
	 */
	public synchronized void setDebug(boolean debug) {
		this.debug = debug;
		if (debug)
			pr("DEBUG: setDebug: JavaMail version " + Version.version);
	}

	/**
	 * Get the debug setting for this Session.
	 * 
	 * @return current debug setting
	 */
	public synchronized boolean getDebug() {
		return debug;
	}

	/**
	 * Set the stream to be used for debugging output for this session.
	 * If <code>out</code> is null, <code>System.out</code> will be used.
	 * Note that debugging output that occurs before any session is created,
	 * as a result of setting the <code>mail.debug</code> system property,
	 * will always be sent to <code>System.out</code>.
	 * 
	 * @param out
	 *            the PrintStream to use for debugging output
	 * @since JavaMail 1.3
	 */
	public synchronized void setDebugOut(PrintStream out) {
		this.out = out;
	}

	/**
	 * Returns the stream to be used for debugging output. If no stream
	 * has been set, <code>System.out</code> is returned.
	 * 
	 * @return the PrintStream to use for debugging output
	 * @since JavaMail 1.3
	 */
	public synchronized PrintStream getDebugOut() {
		if (out == null)
			return System.out;
		else
			return out;
	}

	/**
	 * This method returns an array of all the implementations installed
	 * via the javamail.[default.]providers files that can
	 * be loaded using the ClassLoader available to this application.
	 * 
	 * @return Array of configured providers
	 */
	public synchronized Provider[] getProviders() {
		Provider[] _providers = new Provider[providers.size()];
		providers.copyInto(_providers);
		return _providers;
	}

	/**
	 * Returns the default Provider for the protocol
	 * specified. Checks mail.&lt;protocol&gt;.class property
	 * first and if it exists, returns the Provider
	 * associated with this implementation. If it doesn't exist,
	 * returns the Provider that appeared first in the
	 * configuration files. If an implementation for the protocol
	 * isn't found, throws NoSuchProviderException
	 * 
	 * @param protocol
	 *            Configured protocol (i.e. smtp, imap, etc)
	 * @return Currently configured Provider for the specified protocol
	 * @exception NoSuchProviderException
	 *                If a provider for the given
	 *                protocol is not found.
	 */
	public synchronized Provider getProvider(String protocol) throws NoSuchProviderException {

		if (protocol == null || protocol.length() <= 0) {
			throw new NoSuchProviderException("Invalid protocol: null");
		}

		Provider _provider = null;

		// check if the mail.<protocol>.class property exists
		String _className = props.getProperty("mail." + protocol + ".class");
		if (_className != null) {
			if (debug) {
				pr("DEBUG: mail." + protocol + ".class property exists and points to " + _className);
			}
			_provider = (Provider) providersByClassName.get(_className);
		}

		if (_provider != null) {
			return _provider;
		} else {
			// returning currently default protocol in providersByProtocol
			_provider = (Provider) providersByProtocol.get(protocol);
		}

		if (_provider == null) {
			throw new NoSuchProviderException("No provider for " + protocol);
		} else {
			if (debug) {
				pr("DEBUG: getProvider() returning " + _provider.toString());
			}
			return _provider;
		}
	}

	/**
	 * Set the passed Provider to be the default implementation
	 * for the protocol in Provider.protocol overriding any previous values.
	 * 
	 * @param provider
	 *            Currently configured Provider which will be
	 *            set as the default for the protocol
	 * @exception NoSuchProviderException
	 *                If the provider passed in
	 *                is invalid.
	 */
	public synchronized void setProvider(Provider provider) throws NoSuchProviderException {
		if (provider == null) {
			throw new NoSuchProviderException("Can't set null provider");
		}
		providersByProtocol.put(provider.getProtocol(), provider);
		props.put("mail." + provider.getProtocol() + ".class", provider.getClassName());
	}

	/**
	 * Get a Store object that implements this user's desired Store
	 * protocol. The <code>mail.store.protocol</code> property specifies the
	 * desired protocol. If an appropriate Store object is not obtained,
	 * NoSuchProviderException is thrown
	 * 
	 * @return a Store object
	 * @exception NoSuchProviderException
	 *                If a provider for the given
	 *                protocol is not found.
	 */
	public Store getStore() throws NoSuchProviderException {
		return getStore(getProperty("mail.store.protocol"));
	}

	/**
	 * Get a Store object that implements the specified protocol. If an
	 * appropriate Store object cannot be obtained,
	 * NoSuchProviderException is thrown.
	 * 
	 * @param protocol
	 * @return a Store object
	 * @exception NoSuchProviderException
	 *                If a provider for the given
	 *                protocol is not found.
	 */
	public Store getStore(String protocol) throws NoSuchProviderException {
		return getStore(new URLName(protocol, null, -1, null, null, null));
	}

	/**
	 * Get a Store object for the given URLName. If the requested Store
	 * object cannot be obtained, NoSuchProviderException is thrown.
	 * 
	 * The "scheme" part of the URL string (Refer RFC 1738) is used
	 * to locate the Store protocol.
	 * <p>
	 * 
	 * @param url
	 *            URLName that represents the desired Store
	 * @return a closed Store object
	 * @see #getFolder(URLName)
	 * @see javax.mail.URLName
	 * @exception NoSuchProviderException
	 *                If a provider for the given
	 *                URLName is not found.
	 */
	public Store getStore(URLName url) throws NoSuchProviderException {
		String protocol = url.getProtocol();
		Provider p = getProvider(protocol);
		return getStore(p, url);
	}

	/**
	 * Get an instance of the store specified by Provider. Instantiates
	 * the store and returns it.
	 * 
	 * @param provider
	 *            Store Provider that will be instantiated
	 * @return Instantiated Store
	 * @exception NoSuchProviderException
	 *                If a provider for the given
	 *                Provider is not found.
	 */
	public Store getStore(Provider provider) throws NoSuchProviderException {
		return getStore(provider, null);
	}

	/**
	 * Get an instance of the store specified by Provider. If the URLName
	 * is not null, uses it, otherwise creates a new one. Instantiates
	 * the store and returns it. This is a private method used by
	 * getStore(Provider) and getStore(URLName)
	 * 
	 * @param provider
	 *            Store Provider that will be instantiated
	 * @param url
	 *            URLName used to instantiate the Store
	 * @return Instantiated Store
	 * @exception NoSuchProviderException
	 *                If a provider for the given
	 *                Provider/URLName is not found.
	 */
	private Store getStore(Provider provider, URLName url) throws NoSuchProviderException {

		// make sure we have the correct type of provider
		if (provider == null || provider.getType() != Provider.Type.STORE) {
			throw new NoSuchProviderException("invalid provider");
		}

		try {
			return (Store) getService(provider, url);
		} catch (ClassCastException cce) {
			throw new NoSuchProviderException("incorrect class");
		}
	}

	/**
	 * Get a closed Folder object for the given URLName. If the requested
	 * Folder object cannot be obtained, null is returned.
	 * <p>
	 * 
	 * The "scheme" part of the URL string (Refer RFC 1738) is used to locate
	 * the Store protocol. The rest of the URL string (that is, the
	 * "schemepart", as per RFC 1738) is used by that Store in a protocol
	 * dependent manner to locate and instantiate the appropriate Folder object.
	 * <p>
	 * 
	 * Note that RFC 1738 also specifies the syntax for the "schemepart" for
	 * IP-based protocols (IMAP4, POP3, etc.). Providers of IP-based mail Stores
	 * should implement that syntax for referring to Folders.
	 * <p>
	 * 
	 * @param url
	 *            URLName that represents the desired folder
	 * @return Folder
	 * @see #getStore(URLName)
	 * @see javax.mail.URLName
	 * @exception NoSuchProviderException
	 *                If a provider for the given
	 *                URLName is not found.
	 * @exception MessagingException
	 *                if the Folder could not be
	 *                located or created.
	 */
	public Folder getFolder(URLName url) throws MessagingException {
		// First get the Store
		Store store = getStore(url);
		store.connect();
		return store.getFolder(url);
	}

	/**
	 * Get a Transport object that implements this user's desired
	 * Transport protcol. The <code>mail.transport.protocol</code> property
	 * specifies the desired protocol. If an appropriate Transport
	 * object cannot be obtained, MessagingException is thrown.
	 * 
	 * @return a Transport object
	 * @exception NoSuchProviderException
	 *                If the provider is not found.
	 */
	public Transport getTransport() throws NoSuchProviderException {
		return getTransport(getProperty("mail.transport.protocol"));
	}

	/**
	 * Get a Transport object that implements the specified protocol.
	 * If an appropriate Transport object cannot be obtained, null is
	 * returned.
	 * 
	 * @return a Transport object
	 * @exception NoSuchProviderException
	 *                If provider for the given
	 *                protocol is not found.
	 */
	public Transport getTransport(String protocol) throws NoSuchProviderException {
		return getTransport(new URLName(protocol, null, -1, null, null, null));
	}

	/**
	 * Get a Transport object for the given URLName. If the requested
	 * Transport object cannot be obtained, NoSuchProviderException is thrown.
	 * 
	 * The "scheme" part of the URL string (Refer RFC 1738) is used
	 * to locate the Transport protocol.
	 * <p>
	 * 
	 * @param url
	 *            URLName that represents the desired Transport
	 * @return a closed Transport object
	 * @see javax.mail.URLName
	 * @exception NoSuchProviderException
	 *                If a provider for the given
	 *                URLName is not found.
	 */
	public Transport getTransport(URLName url) throws NoSuchProviderException {
		String protocol = url.getProtocol();
		Provider p = getProvider(protocol);
		return getTransport(p, url);
	}

	/**
	 * Get an instance of the transport specified in the Provider. Instantiates
	 * the transport and returns it.
	 * 
	 * @param provider
	 *            Transport Provider that will be instantiated
	 * @return Instantiated Transport
	 * @exception NoSuchProviderException
	 *                If provider for the given
	 *                provider is not found.
	 */
	public Transport getTransport(Provider provider) throws NoSuchProviderException {
		return getTransport(provider, null);
	}

	/**
	 * Get a Transport object that can transport a Message of the
	 * specified address type.
	 * 
	 * @param address
	 * @return A Transport object
	 * @see javax.mail.Address
	 * @exception NoSuchProviderException
	 *                If provider for the
	 *                Address type is not found
	 */
	public Transport getTransport(Address address) throws NoSuchProviderException {

		String transportProtocol;
		transportProtocol = getProperty("mail.transport.protocol." + address.getType());
		if (transportProtocol != null)
			return getTransport(transportProtocol);
		transportProtocol = (String) addressMap.get(address.getType());
		if (transportProtocol != null)
			return getTransport(transportProtocol);
		throw new NoSuchProviderException("No provider for Address type: " + address.getType());
	}

	/**
	 * Get a Transport object using the given provider and urlname.
	 * 
	 * @param provider
	 *            the provider to use
	 * @param url
	 *            urlname to use (can be null)
	 * @return A Transport object
	 * @exception NoSuchProviderException
	 *                If no provider or the provider
	 *                was the wrong class.
	 */

	private Transport getTransport(Provider provider, URLName url) throws NoSuchProviderException {
		// make sure we have the correct type of provider
		if (provider == null || provider.getType() != Provider.Type.TRANSPORT) {
			throw new NoSuchProviderException("invalid provider");
		}

		try {
			return (Transport) getService(provider, url);
		} catch (ClassCastException cce) {
			throw new NoSuchProviderException("incorrect class");
		}
	}

	/**
	 * Get a Service object. Needs a provider object, but will
	 * create a URLName if needed. It attempts to instantiate
	 * the correct class.
	 * 
	 * @param provider
	 *            which provider to use
	 * @param url
	 *            which URLName to use (can be null)
	 * @exception NoSuchProviderException
	 *                thrown when the class cannot be
	 *                found or when it does not have the correct constructor
	 *                (Session, URLName), or if it is not derived from
	 *                Service.
	 */
	private Object getService(Provider provider, URLName url) throws NoSuchProviderException {
		// need a provider and url
		if (provider == null) {
			throw new NoSuchProviderException("null");
		}

		// create a url if needed
		if (url == null) {
			url = new URLName(provider.getProtocol(), null, -1, null, null, null);
		}

		Object service = null;

		// get the ClassLoader associated with the Authenticator
		ClassLoader cl;
		if (authenticator != null)
			cl = authenticator.getClass().getClassLoader();
		else
			cl = this.getClass().getClassLoader();

		// now load the class
		Class serviceClass = null;
		try {
			// First try the "application's" class loader.
			ClassLoader ccl = getContextClassLoader();
			if (ccl != null)
				try {
					serviceClass = Class.forName(provider.getClassName(), false, ccl);
				} catch (ClassNotFoundException ex) {
					// ignore it
				}
			if (serviceClass == null)
				serviceClass = Class.forName(provider.getClassName(), false, cl);
		} catch (Exception ex1) {
			// That didn't work, now try the "system" class loader.
			// (Need both of these because JDK 1.1 class loaders
			// may not delegate to their parent class loader.)
			try {
				serviceClass = Class.forName(provider.getClassName());
			} catch (Exception ex) {
				// Nothing worked, give up.
				if (debug)
					ex.printStackTrace(getDebugOut());
				throw new NoSuchProviderException(provider.getProtocol());
			}
		}

		// construct an instance of the class
		try {
			Class[] c = { javax.mail.Session.class, javax.mail.URLName.class };
			Constructor cons = serviceClass.getConstructor(c);

			Object[] o = { this, url };
			service = cons.newInstance(o);

		} catch (Exception ex) {
			if (debug)
				ex.printStackTrace(getDebugOut());
			throw new NoSuchProviderException(provider.getProtocol());
		}

		return service;
	}

	/**
	 * Save a PasswordAuthentication for this (store or transport) URLName.
	 * If pw is null the entry corresponding to the URLName is removed.
	 * <p>
	 * This is normally used only by the store or transport implementations to
	 * allow authentication information to be shared among multiple uses of a
	 * session.
	 */
	public void setPasswordAuthentication(URLName url, PasswordAuthentication pw) {
		if (pw == null)
			authTable.remove(url);
		else
			authTable.put(url, pw);
	}

	/**
	 * Return any saved PasswordAuthentication for this (store or transport)
	 * URLName. Normally used only by store or transport implementations.
	 * 
	 * @return the PasswordAuthentication corresponding to the URLName
	 */
	public PasswordAuthentication getPasswordAuthentication(URLName url) {
		return (PasswordAuthentication) authTable.get(url);
	}

	/**
	 * Call back to the application to get the needed user name and password.
	 * The application should put up a dialog something like:
	 * <p>
	 * 
	 * <pre>
	 * Connecting to &lt;protocol&gt; mail service on host &lt;addr&gt;, port &lt;port&gt;.
	 * &lt;prompt&gt;
	 * 
	 * User Name: &lt;defaultUserName&gt;
	 * Password:
	 * </pre>
	 * 
	 * @param addr
	 *            InetAddress of the host. may be null.
	 * @param protocol
	 *            protocol scheme (e.g. imap, pop3, etc.)
	 * @param prompt
	 *            any additional String to show as part of
	 *            the prompt; may be null.
	 * @param defaultUserName
	 *            the default username. may be null.
	 * @return the authentication which was collected by the authenticator;
	 *         may be null.
	 */
	public PasswordAuthentication requestPasswordAuthentication(InetAddress addr, int port, String protocol, String prompt, String defaultUserName) {

		if (authenticator != null) {
			return authenticator.requestPasswordAuthentication(addr, port, protocol, prompt, defaultUserName);
		} else {
			return null;
		}
	}

	/**
	 * Returns the Properties object associated with this Session
	 * 
	 * @return Properties object
	 */
	public Properties getProperties() {
		return props;
	}

	/**
	 * Returns the value of the specified property. Returns null
	 * if this property does not exist.
	 * 
	 * @return String that is the property value
	 */
	public String getProperty(String name) {
		return props.getProperty(name);
	}

	/**
	 * Load the protocol providers config files.
	 */
	private void loadProviders(Class cl) {
		StreamLoader loader = new StreamLoader() {
			public void load(InputStream is) throws IOException {
				loadProvidersFromStream(is);
			}
		};

		// load system-wide javamail.providers from the <java.home>/lib dir
		try {
			String res = System.getProperty("java.home") + File.separator + "lib" + File.separator + "javamail.providers";
			loadFile(res, loader);
		} catch (SecurityException sex) {
			if (debug)
				pr("DEBUG: can't get java.home: " + sex);
		}

		// load the META-INF/javamail.providers file supplied by an application
		loadAllResources("META-INF/javamail.providers", cl, loader);

		// load default META-INF/javamail.default.providers from mail.jar file
		loadResource("/META-INF/javamail.default.providers", cl, loader);

		if (providers.size() == 0) {
			if (debug)
				pr("DEBUG: failed to load any providers, using defaults");
			// failed to load any providers, initialize with our defaults
			addProvider(new Provider(Provider.Type.STORE, "imap", "com.sun.mail.imap.IMAPStore", "Sun Microsystems, Inc.", Version.version));
			addProvider(new Provider(Provider.Type.STORE, "imaps", "com.sun.mail.imap.IMAPSSLStore", "Sun Microsystems, Inc.", Version.version));
			addProvider(new Provider(Provider.Type.STORE, "pop3", "com.sun.mail.pop3.POP3Store", "Sun Microsystems, Inc.", Version.version));
			addProvider(new Provider(Provider.Type.STORE, "pop3s", "com.sun.mail.pop3.POP3SSLStore", "Sun Microsystems, Inc.", Version.version));
			addProvider(new Provider(Provider.Type.TRANSPORT, "smtp", "com.sun.mail.smtp.SMTPTransport", "Sun Microsystems, Inc.", Version.version));
			addProvider(new Provider(Provider.Type.TRANSPORT, "smtps", "com.sun.mail.smtp.SMTPSSLTransport", "Sun Microsystems, Inc.", Version.version));
		}

		if (debug) {
			// dump the output of the tables for debugging
			pr("DEBUG: Tables of loaded providers");
			pr("DEBUG: Providers Listed By Class Name: " + providersByClassName.toString());
			pr("DEBUG: Providers Listed By Protocol: " + providersByProtocol.toString());
		}
	}

	private void loadProvidersFromStream(InputStream is) throws IOException {
		if (is != null) {
			LineInputStream lis = new LineInputStream(is);
			String currLine;

			// load and process one line at a time using LineInputStream
			while ((currLine = lis.readLine()) != null) {

				if (currLine.startsWith("#"))
					continue;
				Provider.Type type = null;
				String protocol = null, className = null;
				String vendor = null, version = null;

				// separate line into key-value tuples
				StringTokenizer tuples = new StringTokenizer(currLine, ";");
				while (tuples.hasMoreTokens()) {
					String currTuple = tuples.nextToken().trim();

					// set the value of each attribute based on its key
					int sep = currTuple.indexOf("=");
					if (currTuple.startsWith("protocol=")) {
						protocol = currTuple.substring(sep + 1);
					} else if (currTuple.startsWith("type=")) {
						String strType = currTuple.substring(sep + 1);
						if (strType.equalsIgnoreCase("store")) {
							type = Provider.Type.STORE;
						} else if (strType.equalsIgnoreCase("transport")) {
							type = Provider.Type.TRANSPORT;
						}
					} else if (currTuple.startsWith("class=")) {
						className = currTuple.substring(sep + 1);
					} else if (currTuple.startsWith("vendor=")) {
						vendor = currTuple.substring(sep + 1);
					} else if (currTuple.startsWith("version=")) {
						version = currTuple.substring(sep + 1);
					}
				}

				// check if a valid Provider; else, continue
				if (type == null || protocol == null || className == null || protocol.length() <= 0 || className.length() <= 0) {

					if (debug)
						pr("DEBUG: Bad provider entry: " + currLine);
					continue;
				}
				Provider provider = new Provider(type, protocol, className, vendor, version);

				// add the newly-created Provider to the lookup tables
				addProvider(provider);
			}
		}
	}

	/**
	 * Add a provider to the session.
	 * 
	 * @param provider
	 *            the provider to add
	 * @since JavaMail 1.4
	 */
	public synchronized void addProvider(Provider provider) {
		providers.addElement(provider);
		providersByClassName.put(provider.getClassName(), provider);
		if (!providersByProtocol.containsKey(provider.getProtocol()))
			providersByProtocol.put(provider.getProtocol(), provider);
	}

	// load maps in reverse order of preference so that the preferred
	// map is loaded last since its entries will override the previous ones
	private void loadAddressMap(Class cl) {
		StreamLoader loader = new StreamLoader() {
			public void load(InputStream is) throws IOException {
				addressMap.load(is);
			}
		};

		// load default META-INF/javamail.default.address.map from mail.jar
		loadResource("/META-INF/javamail.default.address.map", cl, loader);

		// load the META-INF/javamail.address.map file supplied by an app
		loadAllResources("META-INF/javamail.address.map", cl, loader);

		// load system-wide javamail.address.map from the <java.home>/lib dir
		try {
			String res = System.getProperty("java.home") + File.separator + "lib" + File.separator + "javamail.address.map";
			loadFile(res, loader);
		} catch (SecurityException sex) {
			if (debug)
				pr("DEBUG: can't get java.home: " + sex);
		}

		if (addressMap.isEmpty()) {
			if (debug)
				pr("DEBUG: failed to load address map, using defaults");
			addressMap.put("rfc822", "smtp");
		}
	}

	/**
	 * Set the default transport protocol to use for addresses of
	 * the specified type. Normally the default is set by the
	 * <code>javamail.default.address.map</code> or
	 * <code>javamail.address.map</code> files or resources.
	 * 
	 * @param addresstype
	 *            type of address
	 * @param protocol
	 *            name of protocol
	 * @see #getTransport(Address)
	 * @since JavaMail 1.4
	 */
	public synchronized void setProtocolForAddress(String addresstype, String protocol) {
		if (protocol == null)
			addressMap.remove(addresstype);
		else
			addressMap.put(addresstype, protocol);
	}

	/**
	 * Load from the named file.
	 */
	private void loadFile(String name, StreamLoader loader) {
		InputStream clis = null;
		try {
			clis = new BufferedInputStream(new FileInputStream(name));
			loader.load(clis);
			if (debug)
				pr("DEBUG: successfully loaded file: " + name);
		} catch (FileNotFoundException fex) {
			// ignore it
		} catch (IOException e) {
			if (debug) {
				pr("DEBUG: not loading file: " + name);
				pr("DEBUG: " + e);
			}
		} catch (SecurityException sex) {
			if (debug) {
				pr("DEBUG: not loading file: " + name);
				pr("DEBUG: " + sex);
			}
		} finally {
			try {
				if (clis != null)
					clis.close();
			} catch (IOException ex) {
			} // ignore it
		}
	}

	/**
	 * Load from the named resource.
	 */
	private void loadResource(String name, Class cl, StreamLoader loader) {
		InputStream clis = null;
		try {
			clis = getResourceAsStream(cl, name);
			if (clis != null) {
				loader.load(clis);
				if (debug)
					pr("DEBUG: successfully loaded resource: " + name);
			} else {
				/*
				 * if (debug)
				 * pr("DEBUG: not loading resource: " + name);
				 */
			}
		} catch (IOException e) {
			if (debug)
				pr("DEBUG: " + e);
		} catch (SecurityException sex) {
			if (debug)
				pr("DEBUG: " + sex);
		} finally {
			try {
				if (clis != null)
					clis.close();
			} catch (IOException ex) {
			} // ignore it
		}
	}

	/**
	 * Load all of the named resource.
	 */
	private void loadAllResources(String name, Class cl, StreamLoader loader) {
		boolean anyLoaded = false;
		try {
			URL[] urls;
			ClassLoader cld = null;
			// First try the "application's" class loader.
			cld = getContextClassLoader();
			if (cld == null)
				cld = cl.getClassLoader();
			if (cld != null)
				urls = getResources(cld, name);
			else
				urls = getSystemResources(name);
			if (urls != null) {
				for (int i = 0; i < urls.length; i++) {
					URL url = urls[i];
					InputStream clis = null;
					if (debug)
						pr("DEBUG: URL " + url);
					try {
						clis = openStream(url);
						if (clis != null) {
							loader.load(clis);
							anyLoaded = true;
							if (debug)
								pr("DEBUG: successfully loaded resource: " + url);
						} else {
							if (debug)
								pr("DEBUG: not loading resource: " + url);
						}
					} catch (FileNotFoundException fex) {
						// ignore it
					} catch (IOException ioex) {
						if (debug)
							pr("DEBUG: " + ioex);
					} catch (SecurityException sex) {
						if (debug)
							pr("DEBUG: " + sex);
					} finally {
						try {
							if (clis != null)
								clis.close();
						} catch (IOException cex) {
						}
					}
				}
			}
		} catch (Exception ex) {
			if (debug)
				pr("DEBUG: " + ex);
		}

		// if failed to load anything, fall back to old technique, just in case
		if (!anyLoaded) {
			/*
			 * if (debug)
			 * pr("DEBUG: !anyLoaded");
			 */
			loadResource("/" + name, cl, loader);
		}
	}

	private void pr(String str) {
		getDebugOut().println(str);
	}

	/*
	 * Following are security related methods that work on JDK 1.2 or newer.
	 */

	private static ClassLoader getContextClassLoader() {
		return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
			public Object run() {
				ClassLoader cl = null;
				try {
					cl = Thread.currentThread().getContextClassLoader();
				} catch (SecurityException ex) {
				}
				return cl;
			}
		});
	}

	private static InputStream getResourceAsStream(final Class c, final String name) throws IOException {
		try {
			return (InputStream) AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws IOException {
					return c.getResourceAsStream(name);
				}
			});
		} catch (PrivilegedActionException e) {
			throw (IOException) e.getException();
		}
	}

	private static URL[] getResources(final ClassLoader cl, final String name) {
		return (URL[]) AccessController.doPrivileged(new PrivilegedAction() {
			public Object run() {
				URL[] ret = null;
				try {
					Vector v = new Vector();
					Enumeration e = cl.getResources(name);
					while (e != null && e.hasMoreElements()) {
						URL url = (URL) e.nextElement();
						if (url != null)
							v.addElement(url);
					}
					if (v.size() > 0) {
						ret = new URL[v.size()];
						v.copyInto(ret);
					}
				} catch (IOException ioex) {
				} catch (SecurityException ex) {
				}
				return ret;
			}
		});
	}

	private static URL[] getSystemResources(final String name) {
		return (URL[]) AccessController.doPrivileged(new PrivilegedAction() {
			public Object run() {
				URL[] ret = null;
				try {
					Vector v = new Vector();
					Enumeration e = ClassLoader.getSystemResources(name);
					while (e != null && e.hasMoreElements()) {
						URL url = (URL) e.nextElement();
						if (url != null)
							v.addElement(url);
					}
					if (v.size() > 0) {
						ret = new URL[v.size()];
						v.copyInto(ret);
					}
				} catch (IOException ioex) {
				} catch (SecurityException ex) {
				}
				return ret;
			}
		});
	}

	private static InputStream openStream(final URL url) throws IOException {
		try {
			return (InputStream) AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws IOException {
					return url.openStream();
				}
			});
		} catch (PrivilegedActionException e) {
			throw (IOException) e.getException();
		}
	}
}

/**
 * Support interface to generalize
 * code that loads resources from stream.
 */
interface StreamLoader {
	public void load(InputStream is) throws IOException;
}
